﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Drawing;
using System.Runtime.InteropServices;
using System.Windows.Forms;

namespace Djs.Tools.FileCommander.Components
{
    /// <summary>
    /// Centrální úložiště ikon pro soubory a jejich přípony
    /// </summary>
    public class FileIconStorage
    {
        #region Public rozhraní: GetImageKeyForFile(), GetBitmapForFile(), GetIconForFile(), ImageList
        /// <summary>
        /// Metoda vrátí klíč obrázku pro daný soubor.
        /// Daný klíč je možno použít jako klíč pro obrázek v ImgList, potřebný obrázek tam už je uložen (v době, kdy je vrácen jeho klíč).
        /// </summary>
        /// <param name="fileName"></param>
        /// <returns></returns>
        public static string GetImageKeyForFile(string fileName)
        {
            StorageFileItem file;
            StorageIconInfo icon;
            bool found = Instance._GetInfoForFile(fileName, out file, out icon);
            if (!found) return null;
            return file.ImageKey;
        }
        /// <summary>
        /// Vrátí bitmapu pro daný soubor
        /// </summary>
        /// <param name="fileName"></param>
        /// <returns></returns>
        public static Image GetBitmapForFile(string fileName)
        {
            StorageFileItem file;
            StorageIconInfo icon;
            bool found = Instance._GetInfoForFile(fileName, out file, out icon);
            if (!found || !icon.HasBitmap) return null;
            return icon.Bitmap;
        }
        /// <summary>
        /// Vrátí ikonu pro daný soubor
        /// </summary>
        /// <param name="fileName"></param>
        /// <returns></returns>
        public static Icon GetIconForFile(string fileName)
        {
            StorageFileItem file;
            StorageIconInfo icon;
            bool found = Instance._GetInfoForFile(fileName, out file, out icon);
            if (!found || !icon.HasIcon) return null;
            return icon.Icon;
        }
        /// <summary>
        /// Vrátí Ikonu a/nebo Image pro daný soubor
        /// </summary>
        /// <param name="fileName"></param>
        /// <returns></returns>
        public static StorageIconInfo GetInfoForFile(string fileName)
        {
            StorageFileItem file;
            StorageIconInfo icon;
            bool found = Instance._GetInfoForFile(fileName, out file, out icon);
            return icon;
        }
        /// <summary>
        /// Metoda vrátí klíč obrázku pro daný soubor.
        /// Daný klíč je možno použít jako klíč pro obrázek v ImgList, potřebný obrázek tam už je uložen (v době, kdy je vrácen jeho klíč).
        /// </summary>
        /// <param name="specName"></param>
        /// <returns></returns>
        public static string GetImageKeyForSpec(string specName)
        {
            StorageFileItem file;
            StorageIconInfo icon;
            bool found = Instance._GetInfoForSpec(specName, out file, out icon);
            if (!found) return null;
            return file.ImageKey;
        }
        /// <summary>
        /// Vrátí bitmapu pro daný soubor
        /// </summary>
        /// <param name="specName"></param>
        /// <returns></returns>
        public static Image GetBitmapForSpec(string specName)
        {
            StorageFileItem file;
            StorageIconInfo icon;
            bool found = Instance._GetInfoForSpec(specName, out file, out icon);
            if (!found || !icon.HasBitmap) return null;
            return icon.Bitmap;
        }
        /// <summary>
        /// Vrátí ikonu pro daný soubor
        /// </summary>
        /// <param name="specName"></param>
        /// <returns></returns>
        public static Icon GetIconForSpec(string specName)
        {
            StorageFileItem file;
            StorageIconInfo icon;
            bool found = Instance._GetInfoForSpec(specName, out file, out icon);
            if (!found || !icon.HasIcon) return null;
            return icon.Icon;
        }
        /// <summary>
        /// Vrátí Ikonu a/nebo Image pro daný soubor
        /// </summary>
        /// <param name="fileName"></param>
        /// <returns></returns>
        public static StorageIconInfo GetInfoForSpec(string fileName)
        {
            StorageFileItem file;
            StorageIconInfo icon;
            bool found = Instance._GetInfoForSpec(fileName, out file, out icon);
            return icon;
        }
        /// <summary>
        /// Kompletní ImageList pro veškeré FileIcon, je možno jej sdílet pro všechny FileListy
        /// </summary>
        public static ImageList ImageList { get { return Instance._ImageList; } }
        #endregion
        #region Singleton, instanční proměnné
        protected static FileIconStorage Instance
        {
            get
            {
                if (_Instance == null)
                {
                    lock (_InstLock)
                    {
                        if (_Instance == null)
                        {
                            _Instance = new FileIconStorage();
                        }
                    }
                }
                return _Instance;
            }
        }
        private static FileIconStorage _Instance;
        private static object _InstLock = new object();
        private FileIconStorage()
        {
            this._FileCache = new Dictionary<string, StorageFileItem>();
            this._IconCache = new Dictionary<string, StorageIconInfo>();
            this._DriveCache = new Dictionary<string, System.IO.DriveType>();
            this._ImageList = new ImageList();
        }
        #endregion
        #region private vyhledání klíče / obrázku pro jméno souboru
        /// <summary>
        /// Najde informace o ikoně pro daný soubor
        /// </summary>
        /// <param name="fileName"></param>
        /// <param name="file"></param>
        /// <param name="icon"></param>
        /// <returns></returns>
        private bool _GetInfoForFile(string fileName, out StorageFileItem file, out StorageIconInfo icon)
        {
            file = null;
            icon = null;
            if (String.IsNullOrEmpty(fileName)) return false;

            if (this._HasLoadIconFromSelfFile(fileName))
                file = this._SearchFileForSelf(fileName);
            else
                file = this._SearchFileFromWin(fileName);
            if (file == null) return false;

            // Icon = icon key + image:
            if (!this._IconCache.TryGetValue(file.IconKey, out icon))
            {
                icon = this._LoadIconFile(file.IconKey, file.IconFile, file.IconIndex, false);
                this._IconCache.Add(file.IconKey, icon);
            }

            this._AddToImageList(file, icon);

            return (icon.HasBitmap || icon.HasIcon);
        }
        /// <summary>
        /// Najde informace o ikoně pro daný specifický případ (fixní ikona pro aplikaci)
        /// </summary>
        /// <param name="specName"></param>
        /// <param name="file"></param>
        /// <param name="icon"></param>
        /// <returns></returns>
        private bool _GetInfoForSpec(string specName, out StorageFileItem file, out StorageIconInfo icon)
        {
            file = null;
            icon = null;
            if (String.IsNullOrEmpty(specName)) return false;

            file = this._SearchFileForSpec(specName);

            // Icon = icon key + image:
            if (!this._IconCache.TryGetValue(file.IconKey, out icon))
            {
                icon = this._LoadIconSpec(file.IconKey, specName);
                this._IconCache.Add(file.IconKey, icon);
            }

            this._AddToImageList(file, icon);

            return (icon.HasBitmap || icon.HasIcon);
        }
        /// <summary>
        /// Určí, zda pro daný vstupní soubor se bude ikona načítat z vlastního souboru (lokální exe) nebo pomocí registru Windows, z klíče HKEY_CLASSES_ROOT\(extension).
        /// </summary>
        /// <param name="fileName"></param>
        /// <returns></returns>
        private bool _HasLoadIconFromSelfFile(string fileName)
        {
            string ext = System.IO.Path.GetExtension(fileName).ToLower();
            if (!(ext == ".exe")) return false;
            bool isLocal = this._IsLocalDrivePath(fileName);
            return isLocal;
        }
        /// <summary>
        /// Metoda vrátí true, pokud daná cesta se odkazuje na lokální pevný disk
        /// </summary>
        /// <param name="fileName"></param>
        /// <returns></returns>
        private bool _IsLocalDrivePath(string fileName)
        {
            if (String.IsNullOrEmpty(fileName)) return false;
            string root = System.IO.Path.GetPathRoot(fileName).ToLower();
            if (String.IsNullOrEmpty(root)) return false;
            System.IO.DriveType driveType;
            if (!this._DriveCache.TryGetValue(root, out driveType))
            {
                System.IO.DriveInfo di = new System.IO.DriveInfo(root);
                driveType = di.DriveType;
                this._DriveCache.Add(root, driveType);
            }
            return driveType == System.IO.DriveType.Fixed;
        }
        /// <summary>
        /// Najde záznam File pro lokální EXE soubor
        /// </summary>
        /// <param name="fileName"></param>
        /// <returns></returns>
        private StorageFileItem _SearchFileForSelf(string fileName)
        {
            string key = fileName.ToLower();
            StorageFileItem file;
            if (!this._FileCache.TryGetValue(key, out file))
            {
                string imageKey = "#" + this._FileCache.Count.ToString();
                file = new StorageFileItem(key, imageKey, fileName, 0);
                this._FileCache.Add(key, file);
            }
            return file;
        }
        /// <summary>
        /// Najde záznam File pro příponu daného souboru
        /// </summary>
        /// <param name="fileName"></param>
        /// <returns></returns>
        private StorageFileItem _SearchFileFromWin(string fileName)
        {
            string key = System.IO.Path.GetExtension(fileName).ToLower();
            StorageFileItem file;
            if (!this._FileCache.TryGetValue(key, out file))
            {
                string imageKey = "#" + this._FileCache.Count.ToString();
                file = _CreateFileItemFromWin(imageKey, fileName);
                this._FileCache.Add(key, file);
            }
            return file;
        }
        /// <summary>
        /// Najde/vytvoří a vrátí záznam File pro specifickou ikonu (fixní z hlediska programu)
        /// </summary>
        /// <param name="specName"></param>
        /// <returns></returns>
        private StorageFileItem _SearchFileForSpec(string specName)
        {
            string key = "<" + specName.ToLower() + ">";
            StorageFileItem file;
            if (!this._FileCache.TryGetValue(key, out file))
            {
                string imageKey = "#" + this._FileCache.Count.ToString();
                file = new StorageFileItem(key, imageKey, specName, 0);
                this._FileCache.Add(key, file);
            }
            return file;
        }
        /// <summary>
        /// Nastaví hodnoty pro načtení ikony z da ve Win registru
        /// </summary>
        /// <param name="fileName"></param>
        /// <param name="iconFile"></param>
        /// <param name="iconIndex"></param>
        /// <param name="fileKey"></param>
        private StorageFileItem _CreateFileItemFromWin(string imageKey, string fileName)
        {
            string ext = System.IO.Path.GetExtension(fileName).ToLower();
            string fileKey = ext;
            string iconFile = "";
            int iconIndex = 0;

            if (!String.IsNullOrEmpty(ext))
            {
                string fileClass = WinReg.ReadString(Microsoft.Win32.RegistryHive.ClassesRoot, ext, "", "");                                   // For ".jpeg" returns: "jpegfile"
                if (!String.IsNullOrEmpty(fileClass))
                {
                    string defaulIcon = WinReg.ReadString(Microsoft.Win32.RegistryHive.ClassesRoot, fileClass + "\\DefaultIcon", "", "");      // For ("jpegfile\DefaultIcon", "") returns: "%SystemRoot%\System32\imageres.dll,-72"
                    // DefaultIcon může obsahovat "%1" => default ikona je soubor sám:
                    if (defaulIcon == "%1")
                    {
                        iconFile = fileName;
                        iconIndex = 0;
                        fileKey = fileName.ToLower();
                        return new StorageFileItem(ext, imageKey, iconFile, 0);
                    }
                    if (String.IsNullOrEmpty(defaulIcon))
                        defaulIcon = WinReg.ReadString(Microsoft.Win32.RegistryHive.ClassesRoot, fileClass, "FriendlyTypeName", "");           // For ("textfile", "FriendlyTypeName") returns: "@%ProgramFiles%\Windows NT\Accessories\WORDPAD.EXE,-189"
                    if (!String.IsNullOrEmpty(defaulIcon))
                    {   // Pokud máme určenou ikonu, tak může / nemusí obsahovat čárku:
                        int at = defaulIcon.LastIndexOf(',');
                        if (at > 0)
                        {   // Máme čárku: "C:\inetpub\wwwroot\Noris43\Noris\ClientImages\Win32\Bin\NorisWin32.exe",0
                            iconFile = defaulIcon.Substring(0, at).Trim();
                            string idx = defaulIcon.Substring(at + 1);
                            Int32.TryParse(idx, out iconIndex);
                            // if (iconIndex < 0)
                            //    iconIndex = -iconIndex;
                        }
                        else
                        {
                            iconFile = defaulIcon.Trim();
                            iconIndex = 0;
                        }
                        if (iconFile.StartsWith("\"") && iconFile.EndsWith("\"") && iconFile.Length > 2)
                            // Pokud soubor s ikonou začíná a končí uvozovkami ("C:\inetpub\wwwroot\Noris43\Noris\ClientImages\Win32\Bin\NorisWin32.exe"), pak je odstraníme:
                            iconFile = iconFile.Substring(1, iconFile.Length - 2).Trim();
                        if (iconFile.StartsWith("@"))
                        {
                            iconFile = System.Environment.GetFolderPath(Environment.SpecialFolder.System) + "\\" + iconFile.Substring(1);
                            if (iconFile.EndsWith(".dll"))
                                iconFile = iconFile + ".dll";
                        }
                    }
                }
            }
            return new StorageFileItem(ext, imageKey, iconFile, iconIndex);
        }
        /// <summary>
        /// Načte a vrátí ikonu z daného externího souboru (aplikace: dll, exe), vytvoří pro ní úložiště (StorageIconItem) a to vrátí
        /// </summary>
        /// <param name="iconKey"></param>
        /// <param name="iconFile"></param>
        /// <param name="iconIndex"></param>
        /// <param name="large"></param>
        /// <returns></returns>
        private StorageIconInfo _LoadIconFile(string iconKey, string iconFile, int iconIndex, bool large)
        {
            Icon fileIcon = ExtractIconFromExe(iconFile, iconIndex, large);
            StorageIconInfo icon = new StorageIconInfo(iconKey, fileIcon);
            return icon;
        }
        /// <summary>
        /// Načte a vrátí specifickou ikonu pro daný název, vytvoří pro ní úložiště (StorageIconItem) a to vrátí
        /// </summary>
        /// <param name="iconKey"></param>
        /// <param name="specName"></param>
        /// <returns></returns>
        private StorageIconInfo _LoadIconSpec(string iconKey, string specName)
        {
            Image bitmap = this._LoadBitmapSpec(specName.ToLower().Trim());
            return new StorageIconInfo(iconKey, bitmap);
        }
        private Image _LoadBitmapSpec(string name)
        {
            switch (name)
            {
                case SPEC_FILE: return PicLibrary16.File;
                case SPEC_FILTER: return PicLibrary16.Filter;
                case SPEC_FOLDER: return PicLibrary16.Folder;
                case SPEC_PREVIOUS: return PicLibrary16.Previous;
                case SPEC_REFRESH: return PicLibrary16.Refresh;
                case SPEC_TEXT: return PicLibrary16.Text;
            }
            return null;
        }
        public const string SPEC_FILE = "file";
        public const string SPEC_FILTER = "filter";
        public const string SPEC_FOLDER = "folder";
        public const string SPEC_PREVIOUS = "previous";
        public const string SPEC_REFRESH = "refresh";
        public const string SPEC_TEXT = "text";
        /// <summary>
        /// Zařadí dodanou ikonu do this._ImageList, pokud tam dosud není
        /// </summary>
        /// <param name="file"></param>
        /// <param name="icon"></param>
        private void _AddToImageList(StorageFileItem file, StorageIconInfo icon)
        {
            if (!this._ImageList.Images.ContainsKey(file.ImageKey))
            {
                if (icon.HasIcon)
                    this._ImageList.Images.Add(file.ImageKey, icon.Icon);
                else if (icon.HasBitmap)
                    this._ImageList.Images.Add(file.ImageKey, icon.Bitmap);
            }
        }

        #endregion
        #region private File + Icon Cache
        private Dictionary<string, StorageFileItem> _FileCache;
        private Dictionary<string, StorageIconInfo> _IconCache;
        private Dictionary<string, System.IO.DriveType> _DriveCache;
        private ImageList _ImageList;
        protected class StorageFileItem
        {
            public StorageFileItem(string fileKey, string imageKey, string iconFile, int iconIndex)
            {
                this.FileKey = fileKey;
                this.ImageKey = imageKey;
                this.IconFile = iconFile;
                this.IconIndex = iconIndex;
                this.IconKey = iconFile.ToLower() + "," + iconIndex.ToString();
                this.Exists = (!String.IsNullOrEmpty(iconFile) && System.IO.File.Exists(iconFile));
            }
            /// <summary>
            /// ToLower(): Jméno vstupního souboru (exe na lokálním harddisku) nebo přípona pro jiné soubory nebo exe na jiném než lokálním úložišti
            /// </summary>
            public string FileKey { get; private set; }
            /// <summary>
            /// Klíč obrázku (de facto pořadové číslo), používá se v lokálním ImageListu a v externí aplikaci
            /// </summary>
            public string ImageKey { get; private set; }
            /// <summary>
            /// Soubor obsahující ikonu.
            /// </summary>
            public string IconFile { get; private set; }
            /// <summary>
            /// Index ikony v souboru IconFile.
            /// </summary>
            public int IconIndex { get; private set; }
            /// <summary>
            /// Klíč ikony z hlediska jejího umístění v souboru: klíč = IconFile.ToLower() + "," + IconIndex.ToString().
            /// Více různých vstupních souborů (více různých přípon) může sdílet jednu ikonu:
            /// pak více klíčů FileKey (=přípona) bude mít shodný IconKey. Ikonu pro konkrétní IconKey najdeme v Dictionary s prvky StorageIconItem.
            /// </summary>
            public string IconKey { get; private set; }
            /// <summary>
            /// Ikona má šanci existovat (existuje soubor). Na index se nehledí, ani se netestuje existence ikony v souboru
            /// </summary>
            public bool Exists { get; private set; }
        }
        #endregion
        #region WinApi:IconExtract
        /// <summary>
        /// Load icon from file
        /// </summary>
        /// <param name="file"></param>
        /// <param name="index"></param>
        /// <param name="large"></param>
        /// <returns></returns>
        public static Bitmap ExtractBitmapFromExe(string file, int index, bool large)
        {
            using (Icon icon = ExtractIconFromExe(file, index, large))
            {
                if (icon == null) return null;
                return icon.ToBitmap();
            }
        }
        /// <summary>
        /// Load icon from file
        /// </summary>
        /// <param name="file"></param>
        /// <param name="index"></param>
        /// <param name="large"></param>
        /// <returns></returns>
        public static Icon ExtractIconFromExe(string file, int index, bool large)
        {
            Icon result = null;
            if (!String.IsNullOrEmpty(file) && System.IO.File.Exists(file))
            {
                unsafe
                {
                    int readIconCount = 0;
                    IntPtr[] hDummy = new IntPtr[1] { IntPtr.Zero };
                    IntPtr[] hIconEx = new IntPtr[1] { IntPtr.Zero };

                    try
                    {
                        if (large)
                            readIconCount = ExtractIconEx(file, index, hIconEx, hDummy, 1);
                        else
                            readIconCount = ExtractIconEx(file, index, hDummy, hIconEx, 1);

                        if (readIconCount > 0 && hIconEx[0] != IntPtr.Zero)
                        {
                            Icon extractedIcon = (Icon)Icon.FromHandle(hIconEx[0]).Clone();
                            if (extractedIcon != null)
                                result = (Icon)extractedIcon.Clone();
                        }
                    }
                    catch { }
                    finally
                    {
                        foreach (IntPtr ptr in hIconEx)
                            if (ptr != IntPtr.Zero)
                                DestroyIcon(ptr);

                        foreach (IntPtr ptr in hDummy)
                            if (ptr != IntPtr.Zero)
                                DestroyIcon(ptr);
                    }
                }
            }
            return result;
        }
        [DllImport("Shell32", CharSet = CharSet.Auto)]
        private static unsafe extern int ExtractIconEx(
            string lpszFile,
            int nIconIndex,
            IntPtr[] phIconLarge,
            IntPtr[] phIconSmall,
            int nIcons);

        [DllImport("user32.dll", EntryPoint = "DestroyIcon", SetLastError = true)]
        private static unsafe extern int DestroyIcon(IntPtr hIcon);
        #endregion
    }
    #region class StorageIconInfo : Informace o ikoně (klíč, ikona, bitmapa)
    /// <summary>
    /// StorageIconInfo : Informace o ikoně (klíč, ikona, bitmapa)
    /// </summary>
    public class StorageIconInfo
    {
        public StorageIconInfo(string iconKey, Image bitmap)
        {
            this.IconKey = iconKey;
            this.Bitmap = bitmap;
        }
        public StorageIconInfo(string iconKey, Icon icon)
        {
            this.IconKey = iconKey;
            this.Icon = icon;
        }
        /// <summary>
        /// Soubor obsahující ikonu: pro lokální EXE soubory je to on sám, pokud se čte ikona z WinReg pak je to jméno souboru s ikonou určené podle přípony z WinReg, včetně čísla ikony
        /// </summary>
        public string IconKey { get; set; }
        /// <summary>
        /// Nalezená bitmapa nebo null
        /// </summary>
        public Image Bitmap { get; set; }
        /// <summary>
        /// true pokud má bitmapu
        /// </summary>
        public bool HasBitmap { get { return (this.Bitmap != null); } }
        /// <summary>
        /// Nalezená ikona nebo null
        /// </summary>
        public Icon Icon { get; set; }
        /// <summary>
        /// true pokud má ikonu
        /// </summary>
        public bool HasIcon { get { return (this.Icon != null); } }
    }
    #endregion
}
